<?php
/**
 * dar驱动
 *
 * @package util.dar
 * @version $Id: IDarProvider.php 314 2008-04-26 17:35:55Z iwind $
 * @since 0.0.3
 */
class IDarProvider  extends IObject {
	private $tableMapping = array();
	
	/**
	 * sqlmap对象
	 *
	 * @var ISqlMap
	 * @since 0.0.3
	 */
	private $sqlMap;
	
	/**
	 * Enter description here...
	 *
	 * @var IDbDriver
	 * @since 0.2
	 */
	private $driver;
	
	/**
	 * 构造器
	 *
	 * @param string $sqlMapConfigXml SqlMap配置
	 * @since 0.0.3
	 */
	public function __construct($sqlMapConfigXml) {
		$this->sqlMap = ISqlMapClient::getSqlMap($sqlMapConfigXml);
		$this->driver = $this->sqlMap->getDriver();
	}
	
    /**
     * 取得sqlmap对象
     *
     * @return ISqlMap
     * @since 0.0.3
     */
    public function &getSqlMap() {
    	return $this->sqlMap;
    }
    
    /**
     * Enter description here...
     *
     * @param unknown_type $modelName
     * @return unknown
     * @since 0.0.3
     */
    private function getRandomTable($modelName) {
    	$modelName = strtolower($modelName);
    	$tables = explode(".", $modelName);
    	$newName = array_pop($tables) . "_" . uniqid();
    	$this->tableMapping[$modelName] = $newName;
    	
    	//last model
    	$array = explode(".", $modelName);
    	$lastModel = array_pop($array);
    	$this->tableMapping[$lastModel] = $newName;
    	
    	return $newName;
    }
    
    private function getTableMapping() {
    	return $this->tableMapping;
    }
    
    private function replaceTables($string) {
    	$left = array();
    	$right = array();
    	foreach ($this->tableMapping as $key=>$value) {
    		$left[] = "/\\[" . preg_quote($key, "/") . "\\]/i";
    		$right[] = $value;
    	}
    	return preg_replace($left, $right, $string);
    }
    
    private function getTableAlias($modelName) {
    	$modelName = strtolower($modelName);
    	if (array_key_exists($modelName, $this->tableMapping)) {
    		return $this->tableMapping[$modelName];
    	}
    	return null;
    }
    
    private function parseSql($sql) {
    	return preg_replace_callback("/\\[([\\w\\.]+)\\]/s", array($this, "replaceField"), $sql);
    }
    
    private function parseNativeSql($sql) {
    	return preg_replace_callback("/\\[([\\w\\.]+)\\]/s", array($this, "replaceNativeField"), $sql);
    }
    
	private function replaceNativeField($match) {
    	$array = explode(".", $match[1]);
    	$array = array_diff($array, array(null));
    	$count = count($array);
    	if ($count == 1) {//如果只有模型
    		$table = IDarModelFactory::getInstance()->getModel($array[0])->getTable()->getName();
    		return $this->driver->quoteKeyword($table);
    	}
    	else if ($count == 0) {
    		return $match[0];
    	}
    	$field = array_pop($array);
    	$model = implode(".", $array);
    	$lastModel = array_pop($array);
    	$table = IDarModelFactory::getInstance()->getModel($lastModel)->getTable()->getName();
    	$field = IDarModelFactory::getInstance()->getModel($lastModel)->getTable()->getField($field);
    	//是否为表达式
    	$expression = $field->getExpression();
    	if ($expression != null) {
    		return $this->parseNativeSql($expression);
    	}
    	
    	//取出列名
    	$column = $field->getColumn();
    	return $this->driver->quoteKeyword($table) . "." . $this->driver->quoteKeyword($column);
    }
 
    private function replaceField($match) {
    	$array = explode(".", $match[1]);
    	$array = array_diff($array, array(null));
    	$count = count($array);
    	if ($count <2){
    		$table = $this->getTableAlias($array[0]);
    		return (is_null($table))?$match[0]: $match[0] . " AS " . $this->driver->quoteKeyword($table);
    	}
    	$field = array_pop($array);
    	$model = implode(".", $array);
    	$lastModel = array_pop($array);
    	
    	//表名
    	$table = $this->getTableAlias($model);
    	if (is_null($table)) {
    		$table = IDarModelFactory::getInstance()->getModel($lastModel)->getTable()->getName();
    	}
    	
    	//字段
    	$field = IDarModelFactory::getInstance()->getModel($lastModel)->getTable()->getField($field);
    	
    	//是否为表达式
    	$expression = $field->getExpression();
    	if ($expression != null) {
    		return $this->parseSql($expression);
    	}
    	
    	//取出列名
    	$column = $field->getColumn();
    	return $this->driver->quoteKeyword($table) . "." . $this->driver->quoteKeyword($column);
    }
    
    /**
     * 执行SQL语句
     *
     * @param IDarModel $model 模型
     * @param string $sql SQL
     * @param array $params 参数
     * @return IDbStatement
     * @since 0.1
     */
    public function exec(IDarModel $model, $sql, array $params = null) {
    	$sql = $this->parseNativeSql($sql, $model);
    	return $this->sqlMap->execute($sql, $params);
    }
    
   	 /**
     * 使用SQL查询
     *
     * @param IDarModel $model 模型
     * @param string $sql SQL
     * @param array $params 参数
     * @since 0.1
     */
    public function findBySql(IDarModel $model, $sql, array $params = null) {
    	$sql = $this->parseNativeSql($sql);
    	$all = $this->findAllBySql($model, $sql, $params);
    	return empty($all)?null:$all[0];
    } 
    
     /**
     * 使用SQL查询
     *
     * @param IDarModel $model 模型
     * @param string $sql SQL
     * @param array $params 参数
     * @since 0.1
     */
    public function findAllBySql(IDarModel $model, $sql, array $params = null) {
    	$sql = $this->parseNativeSql($sql);
    	$statement = $this->getSqlMap()->execute($sql, $params);
    	$array = $statement->fetchAll(IDb::FETCH_ASSOC);
    	$result = array();
		foreach ($array as $row) {
			$domain = $model->createDomain();
			$domain->setDarPersist(true);
			$domain->setDarColumnValues($row);
			$result[] = $domain; 
		}
		return $result;
    }
    
    /**
     * 使用SQL语句计算记录行数
     *
     * @param IDarModel $model 模型
     * @param string $sql SQL
     * @param array $params 参数
     * @return integer
     * @since 0.1
     */
    public function countBySql(IDarModel $model, $sql, array $params = null) {
    	$sql = $this->parseNativeSql($sql);
    	$statement = $this->getSqlMap()->execute($sql, $params);
 		return $statement->fetch(IDb::FETCH_COLUMN , 0);
    }
  
	
	/**
     * 检查是否存在
     *
     * @param IDarModel $model 模型
     * @param mixed $param 可以为IDarQuery，也可以为ID，还可以为一组ID
     * @since 0.1
     */
    public function exists(IDarModel $model, $param) {
    	if (is_scalar($param) || is_null($param)) {
    		$query = IDarQuery::create(array("condition"=>"[" . $model->getName() . "." . $model->getTable()->getId()->getName() . "]=:id", "parameters"=>array("id"=>$param)));
    	}
    	else if (is_array($param)) {
    		$columns = $this->quoteArray($param);
    		$query = IDarQuery::create(array("condition"=>"[" . $model->getName() . "." . $model->getTable()->getId()->getName() . "] IN (" . implode(",", $columns) . ")"));
    	}
    	else if ($param instanceof IDarQuery) {
    		$query = $param;
    	}
    	else {
    		throw new IDarException("unrecognized param type");
    	}
    	
    	$count = $this->count($model, $query);
    	return $count > 0;
    }
	
	/**
	 * 查询
	 *
	 * @param IDarModel $model 模型
	 * @param mixed $id ID
	 * @return IDarDomain 域对象
	 */
	public function find(IDarModel $model, $id) {
		$query = new IDarQuery();
		$query->setSize(1);
		$query->setCondition("[" . $model->getName() . "." . $model->getTable()->getId()->getName() . "]=:id");
		$query->addParameter("id", $id);
		return $this->findByQuery($model, $query);	
	}
	
	public function findByQuery(IDarModel $model, IDarQuery $query = null) {
		if ($query != null) {
			$query->setSize(1);
		}
		$array = $this->findAll($model, $query);
		return empty($array)?null:$array[0];
	}
	
	public function findAll(IDarModel $model, IDarQuery $query = null) {
		if ($query == null) {
			$query = new IDarQuery();
		}
		$from = $this->generateFrom($model);
		$hasWhere = $from["hasWhere"];
		if ($from["sql"] == null) {
			return array();
		}
		else {
			$sql = "SELECT " . $from["columns"] . $from["sql"];
		}
 		$sql .= $this->generateWhere($query, $hasWhere);
 		$sql .= $this->generateOrderBy($query);
 		$sql = $this->driver->limit($sql, $query->getSize(), $query->getOffset());
 		return $this->findAllBySql($model, $sql, $query->getParameters());
	 }
	 
	 /**
	  * Enter description here...
	  * 
	  * $fields = array(
	  * 	"fieldName1"=>1,
	  *     "fieldName2"=>2
	  * )
	  *
	  * @param IDarModel $model
	  * @param unknown_type $columns
	  * @return array
	  * @since 0.1
	  */
	 public function findAllByFields(IDarModel $model, array $fields) {
	 	$modelName = $model->getName();
	 	$query = new IDarQuery();
	 	$_fields = array();
	 	foreach($fields as $name=>$value) {
	 		if (!strstr($name, ".")) {
	    		$name = $model->getName() . "." . $name;
	    	}
	 		$tmp = "_" . uniqid();
	 		$_fields[] = "[{$name}]=:{$tmp}";
	 		$query->addParameter($tmp, $value); 
	 	}
	 	$query->setCondition(implode(" AND ", $_fields));
	 	return $this->findAll($model, $query);
	 }
	 
	 /**
     * 查询字段值在数组中的所有行
     *
     * @param IDarModel $model 模型
     * @param array $array 数组
     * @since 0.1
     */
    public function findAllInArray(IDarModel $model, $field, array $array) {
    	if (empty($array)) {
    		return array();
    	}
    	if (!strstr($field, ".")) {
    		$field = $model->getName() . "." . $field;
    	}
    	$query = IDarQuery::create(array(
    		"condition" => "[{$field}] IN (" . implode(",", $this->quoteArray($array)) . ")"
    	));
    	return $this->findAll($model, $query);
    }
	
    public function save(IDarDomain $domain) {
    	$model = $domain->getDarModel();
    	$fields = $model->getTable()->getFields();
    	$columns = array();
    	$values = array();
    	foreach ($fields as $field) {
			if ($field->isVirtual()) {
				continue;
			}
    		$value = $domain->getDarProperty($field->getName());
    		if (!is_null($value)) {
	    		$columns[] = $field->getColumn();
	    		$values[] = $value;
    		}
    	}
    	$sql = "INSERT INTO " . $this->driver->quoteKeyword(IDarHelper::getTableName($model)) . " (" . implode($this->quoteKeywords($columns), ",") . ") VALUES (" . str_repeat("?,", count($columns) - 1) . "?)";
    	$statement = $this->exec($model, $sql, $values);
    	
    	//set status
    	$domain->setDarPersist(true);
    	
    	//set id name
    	$idName = IDarHelper::getIdName($model);
    	$domain->setDarProperty($idName, $this->getSqlMap()->getLastInsertId($idName));
    	
    	return $domain;
    }
    
    
    
    public function update(IDarDomain $domain) {
    	$model = $domain->getDarModel(); 
    	$fields = $domain->getDarModifiedFields();
    	if (empty($fields)) {
    		return $domain;//无修改
    	}
    	$columns = array();
    	$values = array();
    	foreach ($fields as $field) {
			if ($field->isVirtual()) {
				continue;
			}
    		$columns[] = $this->driver->quoteKeyword($field->getColumn()) . "=?";
    		$values[] = $domain->getDarProperty($field->getName());
    	}
    	$values[] = IDarHelper::getIdValue($model, $domain);
    	$sql = "UPDATE " . $this->driver->quoteKeyword(IDarHelper::getTableName($model)) . " SET " . implode(",", $columns) . " WHERE " . $this->driver->quoteKeyword(IDarHelper::getIdColumn($model)) . "=?";
    	$statement = $this->exec($model, $sql, $values);
    	return $domain;
    }
    
    /**
     * 删除域对象并返回影响的行数
     *
     * @param IDarDomain $domain 域对象
     * @return integer
     * @since 0.0.3
     */
    public function delete(IDarDomain $domain) {
    	$model = $domain->getDarModel();
    	$statement = $this->getSqlMap()->execute("DELETE \n FROM " 
    				. $this->driver->quoteKeyword($domain->getDarTable()->getName())
    				. " \n WHERE \n " 
    				. $this->driver->quoteKeyword(IDarHelper::getIdColumn($model))
    				. "=?", 
    				array(IDarHelper::getIdValue($model, $domain))
    			);
    	return $statement->rowCount();
    }
    
    /**
     * 按照查询条件删除所有相关记录
     *
     * @param IDarModel $model 模型
     * @param IDarQuery $darQuery 查询
     * @since 0.2
     */    
    public function deleteAll(IDarModel $model, IDarQuery $query = null) {
    	if ($query == null) {
    		$query = new IDarQuery();
    	}
    	$from = $this->generateFrom($model);
    	$hasWhere = $from["hasWhere"];
    	if ($from["sql"] === null) {
    		return 0;
    	}
    	else {
    		$sql = "DELETE " . $this->driver->quoteKeyword($this->getTableAlias($model->getName())) . $from["sql"];
    	}

	 	if ($query instanceof IDarQuery) {
	 		$sql .= $this->generateWhere($query, $hasWhere);
	 		$sql = $this->driver->limit($sql, $query->getSize(), $query->getOffset());
	 	}
	 	$stmt = $this->exec($model, $sql, $query->getParameters());
	 	return $stmt->rowCount();
    }
    
    public function count(IDarModel $model, IDarQuery $query = null) {
    	if ($query == null) {
    		$query = new IDarQuery();
    	}
    	$from = $this->generateFrom($model);
    	$hasWhere = $from["hasWhere"];
    	if ($from["sql"] === null) {
    		return 0;
    	}
    	else {
    		$sql = "SELECT COUNT(*) " . $from["sql"];
    	}

	 	if ($query instanceof IDarQuery) {
	 		$sql .= $this->generateWhere($query, $hasWhere);
	 		$sql = $this->driver->limit($sql, $query->getSize(), $query->getOffset());
	 	}
	 	return $this->countBySql($model, $sql, $query->getParameters());
    }
    
     /**
     * 取出所有数据表
     * 
     * 结果数组中每一个元素都是一个IDarTable对象
     * 
     * @return array
     * @since 0.1
     */
    public function tables() {
    	$darTables = array();
    	$tableList = $this->driver->getTableList();
    	foreach ($tableList as $tblName) {
    		$darTable = new IDarTable();
    		$darTable->setName($tblName);
   			
    		//fields
    		$fields = $this->driver->describeTable($tblName);
    		foreach ($fields as $row) {
    			$field = new IDarField();
    			$field->setColumn($row["column"]);
    			$field->setPrimaryKey($row["primaryKey"]);
    			$darTable->addField($field);
    		}
    		
    		$darTables[$tblName] = $darTable;
    	}
    	return $darTables;
    }
    
    private function generateFrom(IDarModel $model) {
    	$fields = $model->getTable()->getFields();//表字段
		$tableName = $model->getTable()->getName();//表名称
		$idColumn = $model->getTable()->getId()->getColumn();//ID
	 	if (empty($fields)) {
	 		return null;
	 	}
	 	$hasWhere = false;//是否有WHERE条件从句
	 	$columns = array();//语句中的列
		$tableMapping = array();
		$tableAlias = $this->getRandomTable($model->getName()); 
		$tables = array($this->driver->quoteKeyword($tableName) . " AS " . $this->driver->quoteKeyword($tableAlias) . "\n");
		$wheres = array();
		foreach ($fields as $field) {
			if (!$field->isVirtual()) {
				$columns[] = $tableAlias . "." . $this->driver->quoteKeyword($field->getColumn()) . "\n"; 
			}
			
			$relation = $field->getRelation();
			if ($relation != null && !$relation->isOptional()) {//关系
				$assocTable = $relation->getAssoc()->getTable();
				$table2 = $this->getRandomTable($model->getName() . "." . $relation->getAssoc()->getName());
				$tables[] = $this->driver->quoteKeyword($assocTable->getName()) . " AS " . $this->driver->quoteKeyword($table2) . "\n";
				$wheres[] = $this->driver->quoteKeyword($tableAlias) . "." . $this->driver->quoteKeyword($relation->getMainField()->getColumn()) . "=" . $this->driver->quoteKeyword($table2) . "." . $this->driver->quoteKeyword($relation->getAssocField()->getColumn()) . "\n";
			}				
		}
		if (!empty($wheres)) {
			$hasWhere = true;
			$sql = "FROM\n " . implode(",", $tables) . " WHERE (" . implode(" AND ", $wheres) . ")\n";
		}
		else {
			$sql = "FROM\n " . implode(",", $tables);
		}
		return array("sql"=>$sql, "columns"=>implode(",", $columns), "hasWhere"=>$hasWhere);
    }
    
    private function generateWhere(IDarQuery $query, $hasWhere) {
    	$where = null;
    	$condtion = $this->parseSql($query->getCondition());
 		if ($condtion != null) {
 			$where = $hasWhere?" AND (" . $condtion . ")": " WHERE {$condtion}";
 		}
 		return $where;
    }
    
    private function generateOrderBy(IDarquery $query) {
    	$orders = array();
 		foreach ($query->getOrders() as $order) {
 			if (!is_array($order) || !isset($order["model"]) || !isset($order["field"])) {
 				continue;
 			}
 			$orderModel = $order["model"];
 			$orderField = $order["field"];
 			$column = $this->parseSql("[" . $orderModel . "." . $orderField. "]");
 			if (isset($order["order"]) && in_array($order["order"], IDarQuery::getOrderTypes())) {
 				$column .= " " . $order["order"];
 			}
 			$orders[] = $column;
 		}
 		if (!empty($orders)) {
 			return " ORDER BY " . implode(",", $orders);
 		}
 		return null;
    }
    
    /**
     * 引住数组中的数据
     *
     * @param array $array 数组
     * @return array
     * @since 0.0.3
     */
    private function quoteArray(array $array) {
    	foreach ($array as $key=>$value) {
    		$array[$key] = $this->driver->quote($value);
    	}
    	return $array;
    }
    
    /**
     * 引住数组中的关键字(包括表名，字段名)
     *
     * @param array $array 数组
     * @return array
     * @since 0.2
     */
    private function quoteKeywords(array $array) {
    	foreach ($array as $key=>$value) {
    		$array[$key] = $this->driver->quoteKeyword($value);
    	}
    	return $array;
    }
}

?>